קטגוריה: מסדי נתונים

Sequel חלק שני

בחלק הקודם הצגתי על רגל אחת את הORM שנקרא Sequel והצגה של שימוש בסיסי בו.
בחלק זה, אנסה לתת לכם כמה דגשים על שימוש מעשי יותר בו.

מודל

Sequel תומך באפשרות ליצור מודל -> מחלקה שמייצגת טבלה. אם נחזור למסד הנתונים שיצרתי בחלק הקודם, ניתן ליצור מודל לטבלת Posts:

class Posts < Sequel::Model
end

זה כל מה שאנחנו זקוקים בשביל לגשת אל טבלת Posts. עכשיו התוכנית שלנו מההתחלה עד הסוף תהיה:

require 'rubygems'
require 'sequel'

DB = Sequel::Sqlite('blog.db')

class Posts < Sequel::Model
end

Posts.where('id > :id', 1000).delete

בהתחלה אתחלתי את Sequel, פעולה שלא ניתן לברוח ממנה.
לאחר מכן יצרתי את המודול שלנו (הוא חייב כבר גישה למסד הנתונים).
אחר כך, יצרתי תנאי שאומר להחזיר את כל הרשומות עם id שגדול מ1000, ואז למחוק אותן.
זו הדרך הנכונה ד"א לעשות דברים איתו, כלומר lazy loading של תנאי לפני הפעולה עצמה.

בעת חיפוש, במידה וארצה למשל, לקבל את השדה title, אוכל לעשות זאת כך:

Posts[:title].where(id: 1)

אך במידה וארצה להשפיע על מה יהיה בפעולת ה select, עם יותר משדה אחד, אוכל פשוט להשתמש בפעולה הבאה:

Posts.select(:title).where(id: 1)

יש עוד המון פעולות שמתורגמות למתודות בספרייה, אבל יש לה גם כלי עזר נוספים.
למשל, נגיד ונרצה לבצע pagination למידע.
כלומר נרצה לקבל כל פעם איקס כמות ערכים לדף. ולדעת להמשיך משם.
כאן אנחנו מרגישים את הספרייה יותר, היות ויש לה כלים אפלו לשם כך:

Posts.paged_each(rows_per_fetch: 25) do |row|
  p row
end

מה שקורה כאן הוא שבפועל ריבוי שאילתות המחשבות עבורינו את המידע,אך אנחנו מקבלים רק 25 תוצאות ל"דף".

עוד תמיכה מעניינת, היא לקחת מידע SQL ולקבל כלים שמאפשרים לבצע פעולות של Graph. כלומר מיפוי טבלאות וערכים בצורה של גרף. כלומר שיוך מידע בין טבלאות שונות, כולל האפשרות ליצור alias לטבלאות שונות בשביל להביא את המידע בפועל.

בחלק הבא אדבר על התמיכה בjoin כחלק מתחביר ואסיים עם הסבר על תמיכה בmigration ובשורת הפקודה אשר נקראת sequel.

Sequel חלק ראשון

הקדמה

נכתבו עבור עבודה בשפת רובי מספר ספריות ORM. הספרייה המוכרת ביותר (ובעיני הבעייתית ביותר) היא ActiveRecord המגיעה כחלק מ Rails, אך עומדת גם בינה לבין עצמה.
ספריית ORM אחרת שאני מחבב בערך מהניסוי השני שלי (בניסוי הראשון לא הבנתי אותה), נקראת Sequel.

היא עובדת מצד אחד בגישה שונה מActiveRecord, כך שלהגר אליה אינו דבר של מה בכך, אך לאחר שמבינים אותה, מגלים ספרייה עם יותר כוח, אבל בעיקר שכתובה ועובדת נכון יותר (לדעתי).
ביולי הספרייה הגיעה לגרסה 4.0.0, והחלטתי שהגיע הזמן גם לדבר עליה, כאשר בחלק השני, אצור גם דוגמאות חזקות יותר, ואפילו כיצד יוצרים סוג של migration איתה כדוגמת צורת העבודה הנהוגה ב Rails.

על הספרייה

הספרייה מחולקת לשני חלקים:

  1. חלק ראשון הוא ה core, אשר עובד בצורה זהה, וזקוק מאחורי הקלעים לחיבור מסדי נתונים.
  2. החלק השני הוא חלק התוספים, אשר מסוגלים להוסיף לספרייה יכולות נוספות, אך צריך לאפשר אותם. לצערי עדיין לא התעסקתי עם חלק התוספים, ולכן נכון לפוסט זה, עדיין לא אוכל לכתוב עליהם דבר מה.

התקנה

על מנת להתקין את sequel, דבר ראשון נתקין אותו, ודבר שני את הדריבר הרגיל לחיבור מסד הנתונים:

$ gem install sqlite3 sequel

התחלה

להמשיך לקרוא

Firebird פוגש את ליברה אופיס

פרוייקט הפיתוח GSOC מבית גוגל כמובן, הביא לכך שנכון לשבוע שעבר, יש דריבר רשמי לFirebird בתוך LibreOffice Base.

הדריבר כרגע לא עושה כמעט כלום, כי זו רק התחלת הפיתוח, אבל הוא נבנה בעצם לאפשר לעבוד עם Firebird בתצורת Embedded כחלק מBase של ליברה.
כרגע אין תמיכה בקובץ חיצוני, או שרת חיצוני, אך מדברים על להוסיף תמיכה שכזו.

על מנת לגרום לו לעבוד בקובץ odb, יש לשנות הגדרות של

EmbeddedDatabases/DefaultEmbeddedDatabase/Value

מהערך של

sdbc:embedded:hsqldb

לערך:

sdbc:embedded:firebird

כמו כן, ניתן תמיד ליצור קובץ חדש ולהגדיר אותו לעבוד עם Firebird.

הבלוג המקורי בנושא. קוד המקור בgit.
 

ActiveRecord sucks (English)*‎

Every Ruby developer knows an ORM named ActiveRecord.
It is so common, that it has even implementation on different programming languages, such as PHP.

At one hand, it is written in a way that truly understand Ruby, but on the other hand, it is written very badly. It is written so badly, that it has a lot of SQLi bugs inside, it uses too many resources to perform tasks, and the list just goes on and on …

If that was not enough, then it is very opinionated on how things would be created, even on places that you need different level of thinking, it does not provide it on it's own, and you find yourself write stored procedures that implement them, or functions for triggers instead.

I mostly use Ruby "clean", that is not writing any web based application, and on one of the non web program that I wrote, I needed to go over million records per day (not literally a day, but day in BI), and I find that after 200 requests for records, it opens too many connections to the database, even though it has a limit for "only" 120 connections for it's connection pool, but when I decrease it for 50 connections, it claims that it does not have enough connections to use.
Even an hour of inactivity, it does not release it's connections back, even though it has a "timeout" of 30 seconds to release it.

It ignores my timeout settings, it just take more resources, but does not release them back, regardless of what I tell it to do. So it renders as resource hog, and you require to release the resources "by hand" rather then do it's job right.

I found, many complaints about it on the web, and even few bug fixes, that didn't really fixed anything as I can see in real life situation, even when going to the last stable release of it.

S I started looking at other ORM for Ruby (and there are others as well), and it looks like a problem only with AR so far.

* This is a translation that was requested by few people for the original Hebrew post.

ActiveRecord sucks

מתכנתי רובי (בין אם מסילות או לא), מכירים את ה ORM שנקרא ActiveRecord.
הוא כל כך נפוץ, עד שנוצרו לו אפילו חיקויים בשפות אחרות כדוגמת PHP.

הבעיה היא שהוא כתוב ממש רע. מצד אחד הוא משתמש בחשיבה של רובי בצורה באמת מטורפת, אבל הוא לא יעיל יחסית מבחינת משאבים, יש בו המון באגים שיוצרים SQLi ועוד בעיות אחרות בסגנון.
אם כל זה לא היה מספיק, אז בנוסף הוא יותר מידי דעתני גם במקומות שחייבים לגלות גמישות מחשבתית, ואני מוצא את עצמי מבצע הרבה דברים בטריגרים של מסד נתונים, למרות שהיה מתאים לעשות אותם יותר בתוכנה שלי.

למשל יש לי מערכת לא מבוססת web בשום צורה, שעובדת עם מליוני רשומות ביום. הבעיה היא שכבר אחרי 200 גישות למסד הנתונים, למרות שה pool מוגדר רק ל120 חיבורים, אני מגלה שנפתחו עוד חיבורים שלא נסגרים. אם אני מוריד את זה ל50, אז הוא מתלונן שזה לא מספיק, אבל גם אחרי שעה של חוסר פעילות, הוא לא מסיים את החיבורים למסד הנתונים, וזה למרות שמוגדר לו להחזיר את החיבור לאחר 30 שניות ללא שימוש.

לא משנה שהגדרתי לו timeout לכל פעולה, הוא לא משחרר, רק לוקח עוד. חשבתי שאני לא בסדר, אבל מצאתי כל כך הרבה אנשים שמתלוננים על זה כבר שנים, מה שהופך אותו ללא יעיל אם אין לי משהו שאני מריץ שיגרום לו לשחרר משאבים ידנית.

היו דיווחי באגים שונים, ו"תיקונים" והבעיה פשוט ממשיכה, לא משנה שאני תמיד מנסה לשדרג לגרסה הכי אחרונה.

אז התחלתי לבדוק ORM אחרים ברובי (כן יש כמה וכמה), ונראה שזו בעיה ייחודית ל AR בלבד.

My Database-free Application lecture

Today I gave a lecture about database-free applications.

Most people who first read the title, thought that "oh you probably write to memory or disk or something", yet the lecture is not about storing data, but on one approach of designing systems to work.

It is a completely different thing, yet I find that many people can't see it, they are looking for the "catch".

This post was written prior to the actual lecture itself (a day before in fact 🙂 ), so I can't tell if my lecture was good enough or not, but you have my slide notes, and the whole lecture (using html5 and reveal.js -> firefox or chrome recommended), so take a look and tell me yourself 🙂

My first python application

It's that time of the thing again – I'm required to learn new technology due to a project requirement.
The requirement is to write something using django, but I do not know django or Python.

So I've started my first project using the Python language, just to have a feel for it.

My first project is to convert my (in)famous quote file into a database, so I choose Firebird db for the task, using Python driver named fdb. It is very robust open source RDBMS, that many people over look, and choose unreliable RDBMS known as MySQL/MariaDB instead (but why ?!).
Here is the DDL of my database, it's simple as you can see, but has so much abilities 🙂

My code is not perfect, and require a lot more optimization, and polishing, but I'm getting there using the help of my friend – Meir, who loves the idea that a Ruby dev like myself arriving into the python world 🙂

בדיקות אוטומטיות אינן התשובות להכל

TDD – ‏Test Driven Development היא גישה או תפיסה האומרת כי כל שורת קוד אותה אנחנו מפתחים, אנחנו קודם יוצרים לה בדיקה שתיכשל, ואז כותבים את הקוד שיגרום לבדיקה לעבור, ובכך מונעים הרבה מאוד בעיות בפיתוח, וכן יכולים לזהות ריגרסיה כאשר זו מגיעה בקוד. כבר כתבתי על הנושא מספר פעמים כאן בבלוג.

לאחרונה היה באג בספרייה בשם ActiveRecord, אשר במצב מאוד מיוחד, היה ניתן לגרום למתודה find_by_‎ לבצע SQL Injection. הבאג ד"א היה ניתן לניצול רק במצבים מאוד מדוייקים של שימוש במתודות, ולכן אם לא היה שימוש בגישה מסויימת, לא היה ניתן לנצל אותם, אבל הבאג היה עדיין קיים, והמליצו לכולם לשדרג לתיקון הבעיה.

בגדול מאוד, הבאג התרחש רק במצב קיצון מסויים. הבאג מאוד "פשוט": הספרייה לא תרגמה תוכן כמחרזות, אלא פרשה את זה כאובייקט רובי, ואז היה ניתן להזריק קוד (בעיקר ל SQL). העניין הוא, כי במרבית השימושים במתודה, הבאג לא התקיים.

כלומר מרבית השימושים בActiveRecord מעולם לא היו גורמים לבעיות או מסכנים אתר מסויים ב SQLi. ועכשיו נשאלת השאלה: האם TDD מסוגל לעלות על מקרה קצה כל כך קיצוני ?

האם ב99.9 אחוז מהבדיקות שתעשה הבדיקה תצא תקינה, האם תדע לכתוב את הבדיקה 0.1 בה תצליח לגלות באג ?

לדעתי האישית, התשובה היא – כי לרוב לא. יש גבול לרמת הבדיקות שניתן לבצע בשביל לעלות על בעיות.
כלומר יש עדיין הרבה מאוד גורמים שלא ניתן לחשוב עליהם מראש שיכולים לגרום לקוד שלנו לדווח על באג, אבל לא ניתן לכתוב אין סוף בדיקות לכך.

בדיקות אוטומטיות כן חשובות, ואינני אומר לרגע אחד לוותר עליהן, אבל הן אינן תחליף לבדיקות נוספות, כדוגמת QA איכותי, וכן מבחן "המשתמש האידיוט" שעושה מה שבא לו בתוכנה, והתוכנה צריכה לדעת להתמודד עם זה.

בדיקות אוטומטיות בסופו של דבר באות להוכיח כי מה שבודקים באמת מתקיים, אבל הן אינן מוכיחות כי אין באגים בקוד, אלא העדר באגים במה שנבדק, אלא אם הבדיקה עצמה מכילה באג זה או אחר.

gbak ויכולותיו

ל Firebird ישנם כיום (החל מגרסה 2), שתי תוכנות לביצוע גיבויים של מסד הנתונים, כל אחת מספקת יכולות שונות מחברתה. כאשר לפני גרסה 2, היא החזיקה רק תוכנה אחת בשם gbak.

התוכנה מאפשרת לבצע "גיבוי חם" – כלומר גיבוי בזמן העבודה על מסד הנתונים ללא גרימת נזק כלשהו. אבל זה לא החלק המעניין באמת בנושא.

יש לה הרבה יכולות כדוגמת:

  • המרה בין סביבה לסביבה (למשל Little/Big Endian,‏ 32 מול 64 ביט וכו)
  • דחיסת מידע וניקוי המידע שנשמר ממחיקות (למשל)
  • שינוי פעולות אבטחה, כדוגמת שינוי משתמש ו/או סיסמה למסד הנתונים
  • גיבוי למספר קבצים קטנים את המידע של מסד הנתונים
  • שדרוג בין גרסאות של השרת, ובכך להמיר דברים לפי הצורך

ועוד פעולות נוספות, כדוגמת שחזור אותו המידע 🙂

כמו כן, אין הגבלה בביצוע הגיבויים למיקום של מסד הנתונים, כלומר ניתן להתחבר למסד נתונים שממוקם במקום אחר ולבצע את הגיבוי אצלך על המחשב.

לאחרונה נכתב פוסט המתאר כי שימוש במילים stdin ו stdout מפורשות מילולית, וכתיבתן אינה מתייחסת להתקנים עצמם הקיימים במערכות יוניקס שונות. הסיבה לכך מסתבר, היא שהפעולות שgbak זקוק להן, אינן נתמכות בהתקנים האלו, כדוגמת ביצוע seek על מידע, ולכן כאשר הפקודה רואה את המילים הללו, היא מבינה כי היא צריכה לזרוק פלט למסך או לקרוא קלט ממנו, כלומר התוכנה מתייחסת למילים אלו בצורה מפורשת.

העניין הוא, שFirebird מסוגל לרוץ גם במערכות כדוגמת Windows, בהם אין התקנים כאלו בכלל, אבל עדיין הפקודה תומכת במילים הללו, אשר כאמור מפורשות מילולית.

עוד קצת מידע בנושא מתעוד רשמי של הפקודה.

IPC באמצעות Redis ורובי

Redis הוא מסד נתונים אשר אינו מבוסס SQL וכתבתי עליו רבות כאן בעבר.

לאחרונה נדרשתי לפרק מערכת שלי למספר תתי מערכות, ולהעביר מידע בניהם. כמובן שיש הרבה מאוד דרכים לעשות זאת, כדוגמת Message Queue ‏(RabitMQ, ZeroMQ וכו'), עבודה עם PIPES ביוניקס, עבודה בתצורת client server ועוד הרבה מאוד דרכים כיד הדמיון והמערכת הטובה לכם. אני החלטתי ללכת על Redis.

הסיבה לכך פשוטה: אני צריך לבצע פעולה, ואחרי שתפסתי את האירוע, המידע לא צריך להישמר בזיכרון. יותר מזה, אם וכאשר יש הפסקת חשמל, אתחול או אפילו איבוד מידע, אזי אין לי צורך לבצע שוב את הפעולה. כלומר היא נכונה רק לשלב שבו היא נשלחה.

דבר נוסף, הוא שהמערכות שלי כתובות ברובי, ורק מה שצריך לקבל כאירוע לא נכתב, וRedis גם ככה כבר מותקן, ואני כבר מכיר אותו, ויש לי מעט מאוד זמן לכתוב את השינויים.

לRedis יש מספר דרכים לשלוח ולקבל אירועים, אבל אני הולך לדבר כאן על Publish ו PSubscribe. האחרון מאפשר להאזין להודעות באמצעות תבנית חיפוש (עם כוכביות), במקום שם מלא ומדוייק שאחיו Subscribe מספק.

לשם כך צריך לפחות 2 פרוססים שונים, האחד שולח הודעות, והשני מקבל.

שליחת הודעות מאוד פשוטה:

require 'rubygems'
require 'redis'

redis = Redis.new(:host => 'localhost')

10.times do |i|
  redis.publish("number_#{i + 1}", Marshal.dump({:number => i}))
  sleep( (i + 1) / 10.0)
end

התחברתי לRedis, והתחלתי לרוץ על כל המספרים מ0 ועד 9.
יצרתי מפתח שההתחלה שלו הוא number_‎ ולאחר מכן המספר. המבנה חשוב מאוד בשביל להצליח להאזין לאירוע.
לאחר מכן, יצרתי תצורה בינארית עבור Ruby Hash ושלחתי אותה כמידע שאני זקוק לו לשימוש.
כאשר הפעולה מסתיימת, אני נח כמות של מילישניות לפי המספר, החישוב לדעתי די ברור כאן.

הפרוסס השני צריך להקשיב לאירוע:

require 'rubygems'
require 'redis
redis = Redis.new(:host => 'localhost')

redis.psubscribe('number_*') do |on|
  on.pmessage do |pattern, event, message|
    puts "pattern: #{pattern}, event: #{event}, message: #{Marshal.load(message)}"
  end
end

כאן אנחנו מקשיבים לפעולות אשר נשלחות עם סוגי publish השונים.
יש האזנה לאירועים באמצעות תבנית (pattern) ומכאן המקדם בעצם של הפקודה (p).
הפקודה בעצם מכניסה אותנו לתוך בלוק אשר דרכו אפשר להאזין למגוון סוגים של אירועים.
כן האירוע שצריך הוא pmessage לו אני מאזין, וכאשר הוא מתרחש, הקוד בתוכו מורץ.
אני משחזר כאן את המידע הבינארי חזרה למחלקות של רובי, ובכך העברתי מידע סיריאלי לחלוטין של רובי.
חשוב להבין כי זוהי פעולה שהיא blocking, ולכן כאשר מריצים אותה, אין המשך כלפי מטה לקוד.

התוצר כאן יהיה בסגנון הבא:

pattern: number_*, event: number_1, message: {:number=>0}
pattern: number_*, event: number_2, message: {:number=>1}
pattern: number_*, event: number_3, message: {:number=>2}
pattern: number_*, event: number_4, message: {:number=>3}
pattern: number_*, event: number_5, message: {:number=>4}
pattern: number_*, event: number_6, message: {:number=>5}
pattern: number_*, event: number_7, message: {:number=>6}
pattern: number_*, event: number_8, message: {:number=>7}
pattern: number_*, event: number_9, message: {:number=>8}
pattern: number_*, event: number_10, message: {:number=>9}